process.env.NODE_ENV = 'development'
process.env.BABEL_ENV = 'development';

const { normalizationArgv } = require('./utils/normalization'),
  chalk = require('chalk'),
  argv = require('minimist')(process.argv.slice(2));

const { app } = normalizationArgv(argv)
console.log(chalk.greenBright(`编译应用: ${app}`))

const webpack = require('webpack'),
  WebpackDevServer = require('webpack-dev-server'),
  configFactory = require('../webpack.config'),
  formatWebpackMessages = require('./formatWebpackMessages'),
  createDevServerConfig = require('../webpackDevServer'),
  clearConsole = require('./utils/clearConsole'),
  choosePort = require('./utils/choosePort');

// 环境变量处理
require('./utils/env').genEnv(app)

const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
const HOST = process.env.HOST || '0.0.0.0';

// 端口号校验 -> 确认端口是否存在占用问题

if (process.env.HOST) {
  console.log(
    chalk.cyan(
      `Attempting to bind to HOST environment variable: ${chalk.yellow(
        chalk.bold(process.env.HOST)
      )}`
    )
  );
  console.log(
    `If this was unintentional, check that you haven't mistakenly set it in your shell.`
  );
  console.log(
    `Learn more here: ${chalk.yellow('https://cra.link/advanced-config')}`
  );
  console.log();
}

// port 选择 & build 构建
choosePort(HOST, DEFAULT_PORT)
  .then(build)
  .catch(err => {
    if (err && err.message) {
      console.log(err.message);
    }
    process.exit(1);
  });

// build
function build(_port) {
  if (_port == null) {
    return;
  }
  // 获取 webpack 配置信息
  const config = configFactory({ env: process.env.NODE_ENV, name: app })

  // 创建 compiler
  let compiler;
  try {
    compiler = webpack(config);
  } catch (err) {
    console.log(chalk.red('Failed to compile.'));
    console.log();
    console.log(err.message || err);
    console.log();
    process.exit(1);
  }

  // 注册 hooks ，'invalid' 失效事件，当文件发生变化时并且webpack重新编译这个bundle，当 refresh 时，这将保存替代旧的bundle
  compiler.hooks.invalid.tap('invalid', () => {
    // 清空控制台现有信息
    clearConsole();

    console.log('Compiling...');
  });

  // TODO ts检查钩子注册

  // 注册 hooks ， 'done' 事件，当webpack完成重新编译模块
  compiler.hooks.done.tap('done', async stats => {
    // clearConsole();

    // We have switched off the default webpack output in WebpackDevServer
    // options so we are going to "massage" the warnings and errors and present
    // them in a readable focused way.
    const statsData = stats.toJson({
      all: false,
      warnings: true,
      errors: true,
    });

    const messages = formatWebpackMessages(statsData);
    const isSuccessful = !messages.errors.length && !messages.warnings.length;
    if (isSuccessful) {
      console.log(chalk.green('Compiled successfully!'));
    }
    // if (isSuccessful && (isInteractive || isFirstCompile)) {
    //   printInstructions(appName, urls, useYarn);
    // }
    // isFirstCompile = false;

    // If errors exist, only show errors.
    if (messages.errors.length) {
      // Only keep the first error. Others are often indicative
      // of the same problem, but confuse the reader with noise.
      if (messages.errors.length > 1) {
        messages.errors.length = 1;
      }
      console.log(chalk.red('Failed to compile.\n'));
      console.log(messages.errors.join('\n\n'));
      return;
    }

    // Show warnings if no errors were found.
    if (messages.warnings.length) {
      console.log(chalk.yellow('Compiled with warnings.\n'));
      console.log(messages.warnings.join('\n\n'));

      // Teach some ESLint tricks.
      console.log(
        '\nSearch for the ' +
        chalk.underline(chalk.yellow('keywords')) +
        ' to learn more about each warning.'
      );
      console.log(
        'To ignore, add ' +
        chalk.cyan('// eslint-disable-next-line') +
        ' to the line before.\n'
      );
    }
  });
  // 获取 webpack-dev-server 配置信息
  const serverConfig = {
    ...createDevServerConfig(app, null),
    host: HOST,
    // 使用处理后的端口
    port: _port,
  }
  // webpack-dev-server
  const devServer = new WebpackDevServer(serverConfig, compiler)
  // 启动
  devServer.startCallback(() => {
    // clearConsole();
  })
}